package com.hp.usage.gr.initialization;

import com.hp.siu.collector.rules.RuleException;
import com.hp.siu.logging.Level;
import com.hp.siu.logging.Logger;
import com.hp.siu.utils.Attribute;
import com.hp.siu.utils.NormalizedMeteredEvent;
import com.hp.usage.gr.db.DbBasedRule;
import org.yaml.snakeyaml.Yaml;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

/**
 * Created by huxiaoji on 1/6/2016.
 */
public class GrInitialization extends DbBasedRule{
    private static Logger log_ = Logger.getLogger("com.hp.usage.gr.initialization.GrInitialization", "com.hp.usage.gr.message_catalog");

    @Override
    public void applyRule(NormalizedMeteredEvent nme) throws RuleException {
        if (log_.isLoggable(Level.DEBUG4)){
            log_.logDebug4("applyRule(" + nme + ")");
        }
        boolean forceInitial = false;
        Attribute obj = nme.getAttribute("GR_FORCE_INITIAL_FLAG");
        if (obj != null){
            forceInitial = Boolean.parseBoolean(obj.toString());
        }
        obj = nme.getAttribute("GR_CONFIGURE_FILE");
        if (obj == null) {
            throw new RuleException("GR_CONFIGURE_FILE is mandatory for rule.");
        }
        File file = new File(obj.toString());
        FileInputStream in = null;
        try {
            in = new FileInputStream(file);
            Map<String, Object> configObj = (Map<String, Object>) new Yaml().load(in);

            if (log_.isLoggable(Level.DEBUG4)){
                log_.logDebug4("GR initial configure content: " + configObj);
            }

            validate(configObj);

            Connection conn = null;
            try {
                conn = getConnection();
            } catch (SQLException e) {
                throw new RuleException("Initial database connection failed.");
            }
            try {
                initialDatabase(conn, configObj, forceInitial);

                conn.commit();
            } catch (SQLException e) {
                log_.logError(e.getMessage());
                try {
                    conn.rollback();
                } catch (SQLException e1) {
                    log_.logError(e1.getMessage());
                }
                throw (RuleException) new RuleException("Update database failed, " + e.getMessage()).initCause(e);
            } finally {
                releaseConnection(conn);
            }
        } catch (FileNotFoundException e) {
            throw new RuleException("Can't read configuration file from " + file.getAbsolutePath());
        } finally {
            if (in != null)
                try {
                    in.close();
                } catch (IOException e) {
                    log_.logError("Close configuration file failed, " + e);
                }
        }
    }

    private void initialDatabase(Connection conn, Map<String, Object> configObj, boolean forceInitial) throws SQLException{
        boolean initConf = false, initRegion = false, initSite = false, initRole = false;
        boolean initSiteStatus = false, initSiteConn = false;
        PreparedStatement st = conn.prepareStatement("select SessionID from GR_CONFIGURATION");
        ResultSet rs = st.executeQuery();
        if (rs.next()){
            if (forceInitial) {
                if (log_.isLoggable(Level.DEBUG4)){
                    log_.logDebug4("Cleaning GR configuration table.");
                }
                conn.createStatement().executeUpdate("delete from GR_CONFIGURATION");
                initConf = true;
            }
        } else {
            if (log_.isLoggable(Level.DEBUG4)){
                log_.logDebug4("Cleaning GR configuration table.");
            }
            initConf = true;
        }
        rs.close();
        st.close();

        st = conn.prepareStatement("select REGION_ID from GR_DEF_REGION");
        rs = st.executeQuery();
        if (rs.next()){
            if (forceInitial) {
                if (log_.isLoggable(Level.DEBUG4)){
                    log_.logDebug4("Cleaning GR region definition table.");
                }
                conn.createStatement().executeUpdate("delete from GR_DEF_REGION");
                initRegion = true;
            }
        } else {
            initRegion = true;
        }
        rs.close();
        st.close();

        st = conn.prepareStatement("select SITE_ID from GR_DEF_SITE");
        rs = st.executeQuery();
        if (rs.next()){
            if (forceInitial) {
                if (log_.isLoggable(Level.DEBUG4)){
                    log_.logDebug4("Cleaning GR site definition table.");
                }
                conn.createStatement().executeUpdate("delete from GR_DEF_SITE");
                initSite = true;
            }
        } else {
            initSite = true;
        }
        rs.close();
        st.close();

        st = conn.prepareStatement("select REGION_ID from GR_REGION_SITE_ROLE");
        rs = st.executeQuery();
        if (rs.next()){
            if (forceInitial) {
                if (log_.isLoggable(Level.DEBUG4)){
                    log_.logDebug4("Cleaning GR region site role table.");
                }
                conn.createStatement().executeUpdate("delete from GR_REGION_SITE_ROLE");
                initRole = true;
            }
        } else {
            initRole = true;
        }
        rs.close();
        st.close();

        st = conn.prepareStatement("select SITE_ID from GR_SITE_STATUS");
        rs = st.executeQuery();
        if (rs.next()){
            if (forceInitial) {
                if (log_.isLoggable(Level.DEBUG4)){
                    log_.logDebug4("Cleaning GR site status table.");
                }
                conn.createStatement().executeUpdate("delete from GR_SITE_STATUS");
                initSiteStatus = true;
            }
        } else {
            initSiteStatus = true;
        }
        rs.close();
        st.close();

        st = conn.prepareStatement("select SITE_ID_FROM from GR_SITE_INTERCONN_STATUS");
        rs = st.executeQuery();
        if (rs.next()){
            if (forceInitial) {
                if (log_.isLoggable(Level.DEBUG4)){
                    log_.logDebug4("Cleaning GR internal connection status table.");
                }
                conn.createStatement().executeUpdate("delete from GR_SITE_INTERCONN_STATUS");
                initSiteConn = true;
            }
        } else {
            initSiteConn = true;
        }
        rs.close();
        st.close();

        long updateTime = System.currentTimeMillis();
        if (initConf) {
            Map<String, Object> map = (Map<String, Object>) configObj.get("arbiter");
            st = conn.prepareStatement("insert into GR_CONFIGURATION (SessionID, GR_AUTO_SWITCH_FLAG, GR_MONITOR_FLAG, GR_REGION_ID, GR_INITED_FLAG) values (?,?,?,?,?)");
            st.setString(1, "1");
            st.setInt(2, 0);
            st.setInt(3, 0);
            st.setInt(4, getInt(map, "region-id"));
            st.setInt(5, 1);
            st.executeUpdate();

            if (log_.isLoggable(Level.DEBUG4)){
                log_.logDebug4("Initialized GR configuration table.");
            }
            st.close();
        }
        if (initRegion){
            st = conn.prepareStatement("insert into GR_DEF_REGION (REGION_ID,REGION_NAME,DESCRIPTION,LAST_UPDATE_TIME) values (?,?,?,?)");
            List<Map<String, Object>> regions = (List<Map<String, Object>>) configObj.get("region-list");
            for (Map<String, Object> map : regions) {
                st.setInt(1, getInt(map, "id"));
                st.setString(2, getString(map, "name"));
                st.setString(3, getString(map, "description"));
                st.setLong(4, updateTime);
                st.executeUpdate();
            }
            st.close();

            if (log_.isLoggable(Level.DEBUG4)){
                log_.logDebug4("Initialized GR definition table.");
            }
        }

        if (initSite){
            List<Map<String, Object>> sites = (List<Map<String, Object>>) configObj.get("sites-list");
            st = conn.prepareStatement("insert into GR_DEF_SITE (SITE_ID,SITE_NAME,SMAN_URL1,SMAN_URL2,SMAN_USER,SMAN_PASSWORD,DESCRIPTION,LAST_UPDATE_TIME) values (?,?,?,?,?,?,?,?)");
            for (Map<String, Object> map : sites){
                st.setInt(1, getInt(map, "id"));
                st.setString(2, getString(map, "name"));
                st.setString(3, getString(map, "sman-url1"));
                st.setString(4, getString(map, "sman-url2"));
                st.setString(5, getString(map, "sman-user"));
                try {
                    st.setString(6, EncryptUtil.encode(getString(map, "sman-password")));
                } catch (Exception e) {
                    log_.logError("Can't encrypt the password, use plain text indeed." + e.getMessage());
                    st.setString(6, getString(map, "sman-password"));
                }
                st.setString(7, getString(map, "description"));
                st.setLong(8, updateTime);

                st.executeUpdate();
            }
            st.close();

            if (log_.isLoggable(Level.DEBUG4)){
                log_.logDebug4("Initialized GR site definition table.");
            }
        }

        if (initRole){
            List<Map<String, Object>> roles = (List<Map<String, Object>>) configObj.get("region-site-list");
            st = conn.prepareStatement("insert into GR_REGION_SITE_ROLE (REGION_ID,MASTER_SITE_ID,SLAVE_SITE_ID,GR_STATE,LAST_UPDATE_TIME) values (?,?,?,?,?)");
            for (Map<String, Object> map : roles){
                st.setInt(1, getInt(map, "region-id"));
                st.setInt(2, getInt(map, "master-site-id"));
                st.setInt(3, getInt(map, "slave-site-id"));
                st.setInt(4, 1);
                st.setLong(5, updateTime);

                st.executeUpdate();
            }
            st.close();
            if (log_.isLoggable(Level.DEBUG4)){
                log_.logDebug4("Initialized GR region site role table.");
            }
        }

        if (initSiteStatus) {
            List<Map<String, Object>> sites = (List<Map<String, Object>>) configObj.get("sites-list");
            st = conn.prepareStatement("insert into GR_SITE_STATUS (SITE_ID,GR_STATE,STATUS_HEALTH,STATUS_HEALTH_TIME,STATUS_E2E,STATUS_NODES,STATUS_PROCESSES,STATUS_LOGS,STATUS_CONNS,STATUS_IMDB,STATUS_EXT1,STATUS_EXT2,STATUS_EXT3,LAST_UPDATE_TIME) values (?,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,?)");
            for (Map<String, Object> map : sites) {
                st.setInt(1, getInt(map, "id"));
                st.setLong(2, updateTime);
                st.executeUpdate();
            }
            st.close();
            if (log_.isLoggable(Level.DEBUG4)){
                log_.logDebug4("Initialized GR site status table.");
            }
        }

        if (initSiteConn){
            List<Map<String, Object>> roles = (List<Map<String, Object>>) configObj.get("region-site-list");
            st = conn.prepareStatement("insert into GR_SITE_INTERCONN_STATUS (SITE_ID_FROM,SITE_ID_TO,STATUS_PING,LAST_UPDATE_TIME) values (?,?,?,?)");

            for (Map<String, Object> map : roles) {
                int region = getInt(map, "region-id");
                int sitea = getInt(map, "master-site-id");
                int siteb = getInt(map, "slave-site-id");

                st.setInt(1, region);
                st.setInt(2, sitea);
                st.setInt(3, 0);
                st.setLong(4, updateTime);
                st.executeUpdate();

                st.setInt(1, region);
                st.setInt(2, siteb);
                st.setInt(3, 0);
                st.setLong(4, updateTime);
                st.executeUpdate();

                st.setInt(1, sitea);
                st.setInt(2, region);
                st.setInt(3, 0);
                st.setLong(4, updateTime);
                st.executeUpdate();

                st.setInt(1, sitea);
                st.setInt(2, siteb);
                st.setInt(3, 0);
                st.setLong(4, updateTime);
                st.executeUpdate();

                st.setInt(1, siteb);
                st.setInt(2, region);
                st.setInt(3, 0);
                st.setLong(4, updateTime);
                st.executeUpdate();

                st.setInt(1, siteb);
                st.setInt(2, sitea);
                st.setInt(3, 0);
                st.setLong(4, updateTime);
                st.executeUpdate();
            }
            st.close();
            if (log_.isLoggable(Level.DEBUG4)){
                log_.logDebug4("Initialized GR internal connection status table.");
            }
        }
    }

    private void validate(Map<String, Object> configObj) throws RuleException{
        Map<String, Object> arbiter = getMap(configObj, "arbiter");
        assertHasInt(arbiter, "region-id");
        List<Map<String, Object>> regionList = getList(configObj, "region-list");
        List<Map<String, Object>> siteList = getList(configObj, "sites-list");
        List<Map<String, Object>> regionSiteList = getList(configObj, "region-site-list");

        for (Map<String, Object> map : regionList){
            assertHasInt(map, "id");
            assertHasValue(map, "name");
        }
        for (Map<String, Object> map : siteList){
            assertHasInt(map, "id");
            assertHasValue(map, "name");
            assertHasValue(map, "sman-url1");
            assertHasValue(map, "sman-user");
            assertHasValue(map, "sman-password");
        }
        for (Map<String, Object> map : regionSiteList){
            assertHasInt(map, "region-id");
            assertHasInt(map, "master-site-id");
            assertHasInt(map, "slave-site-id");
        }
    }
}
